﻿using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq.Expressions;
using System.Reflection;
using System.Windows;
using System.Windows.Threading;

namespace ItemsViewerDemo
{
    /// <summary>
    /// Base class for the presenter implementations.
    /// </summary>
    /// <remarks>
    /// <para>
    /// All the Presenter classes represents a view of the application with its associated values,
    /// sub-presenters and actions.
    /// </para>
    /// The child classes of <see cref="AbstractViewModel"/> should respect the following contracts:
    /// <list type="bullet">
    /// <item>
    /// All the properties raises the <see cref="AbstractViewModel.PropertyChanged"/> event on update.
    /// </item>
    /// <item>
    /// All the values are simple object (string, int, ...) or business object.
    /// </item>
    /// <item>
    /// All the sub-presenters inherit from the <see cref="AbstractViewModel"/> class.
    /// </item>
    /// <item>
    /// All the actions used the <see cref="Command"/> class to link the command to the
    /// methods of the class.
    /// </item>
    /// </list>
    /// </remarks>
    public abstract class AbstractViewModel : INotifyPropertyChanged, IDisposable
    {
        /// <summary>
        /// Occurs when a property value changes.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged = delegate { };

        /// <summary>
        /// Event when ViewModel is Disposed.
        /// </summary>
        public event EventHandler Disposed;

        /// <summary>
        /// Implementation of IDisposable.Dispose.
        /// </summary>
        public void Dispose()
        {
            if (this.Disposed != null)
            {
                this.Disposed(this, null);
            }

            this.Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// This raises the INotifyPropertyChanged.PropertyChanged event to indicate
        /// a specific property has changed value. This version provides a compile-time safe
        /// way to indicate the property through the use of an expression tree / lambda.
        /// <example>
        /// <code lang="xml">
        /// <![CDATA[
        ///    public string Name
        ///    {
        ///       get { return _name; }
        ///       set
        ///       {
        ///           _name = value;
        ///           OnPropertyChanged(() => Name);
        ///       }
        ///    }
        /// ]]>
        /// </code>
        /// </example>
        /// <typeparam name="T">Type where it is being raised.</typeparam>
        /// <param name="propExpr">The Property.</param>
        /// </summary>
        protected virtual void OnPropertyChanged<T>(Expression<Func<T>> propExpr)
        {
            var prop = (PropertyInfo)((MemberExpression)propExpr.Body).Member;
            this.PropertyChanged(this, new PropertyChangedEventArgs(prop.Name));
        }

        /// <summary>
        /// This raises the INotifyPropertyChanged.PropertyChanged event to indicate
        /// a specific property has changed value.
        /// </summary>
        /// <param name="name">Primary property.</param>
        /// <param name="propertyNames">Additional properties.</param>
        protected virtual void OnPropertyChanged(string name, params string[] propertyNames)
        {
            Debug.Assert(string.IsNullOrEmpty(name) || GetType().GetProperty(name) != null, "The value must always be true.");
            this.PropertyChanged(this, new PropertyChangedEventArgs(name));
            if (propertyNames != null)
            {
                foreach (var propName in propertyNames)
                {
                    this.OnPropertyChanged(propName);
                }
            }
        }

        /// <summary>
        /// This disposes of the view model.
        /// </summary>
        /// <param name="isDisposing">True if IDisposable.Dispose was called.</param>
        protected virtual void Dispose(bool isDisposing)
        { }

        /// <summary>
        /// Helper method to run logic on Dispatcher (UI) thread. Useful for adding to 
        /// ObservableCollection(T), etc.
        /// </summary>
        /// <param name="action">Method to execute.</param>
        protected void DispatcherInvoke(Action action)
        {
            this.DispatcherInvoke(DispatcherPriority.Normal, action);
        }

        /// <summary>
        /// Helper method to run logic on Dispatcher (UI) thread. Useful for adding to 
        /// ObservableCollection(T), etc.
        /// </summary>
        /// <param name="priority">Priority of method.</param>
        /// <param name="action">Method to execute.</param>
        protected void DispatcherInvoke(DispatcherPriority priority, Action action)
        {
            // No application? Just run it.
            if (Application.Current != null)
            {
                Application.Current.Dispatcher.Invoke(priority, action);
            }
            else
            {
                action();
            }
        }

        /// <summary>
        /// Helper method to run logic on Dispatcher (UI) thread. Useful for adding to 
        /// ObservableCollection(T), etc.
        /// </summary>
        /// <param name="action">Method to execute.</param>
        protected void DispatcherBeginInvoke(Action action)
        {
            this.DispatcherBeginInvoke(DispatcherPriority.Normal, action);
        }

        /// <summary>
        /// Helper method to run logic on Dispatcher (UI) thread. Useful for adding to 
        /// ObservableCollection(T), etc.
        /// </summary>
        /// <param name="priority">The Priority.</param>
        /// <param name="action">Method to execute.</param>
        protected void DispatcherBeginInvoke(DispatcherPriority priority, Action action)
        {
            // If no application then just run it.
            if (Application.Current != null)
            {
                if (Application.Current.Dispatcher.CheckAccess() &&
                    priority == DispatcherPriority.Normal)
                {
                    action();
                }
                else
                {
                    Application.Current.Dispatcher.BeginInvoke(priority, action);
                }
            }
            else
            {
                action();
            }
        }
    }
}
